import {BaseSession} from '../base'
import {BoolValue, fromAny, Int32Value, StringValue, toAny} from '../../proto'

const MAX_LONG_CLICK_TIMEOUT = 200

export abstract class WidgetBaseSession extends BaseSession {
    protected widget: HTMLElement;
    private longClick: number = 0;
    private clickTimeOutEvent: any = 0;
    private longEvent: any;
    private clickEvent: any;

    constructor(widgetName: String, render: HTMLElement) {
        super(`Widget${widgetName}`, render)
    }

    onCreate(sessionId: number, futureId: number, ...argv) {
        super.onCreate(sessionId, futureId, ...argv);
        this.widget = this.createWidget(...argv.slice(4))
        const [width, height, left, top] = argv.slice(0, 4)
            .map(n => fromAny(n, Int32Value) + 'px')
        Object.assign(this.widget.style, {position: "absolute", left, top, width, height})
        this.render.append(this.widget)
    }

    abstract createWidget(...argv): HTMLElement;

    onDestroy() {
        super.onDestroy();
        this.widget.remove()
    }

    onReceive(name: String, sessionId: number, futureId: number, ...argv) {
        switch (name) {
            case 'click':
                this.click();
                return;
            case 'focus':
                this.focus()
                return;
            case 'getAccessibilityLabel':
                this.getAccessibilityLabel(sessionId, futureId)
                return;
            case 'setAccessibilityLabel':
                this.setAccessibilityLabel(...argv);
                return;
            case 'setLocation':
                this.setLocation(...argv);
                return;
            case 'setBackgroundColor':
                this.setBackgroundColor(...argv);
                return;
            case 'setOnLongClickListener':
                this.setOnLongClickListener(...argv);
                return;
            case 'setOnClickListener':
                this.setOnClickListener(...argv);
                return;
            case 'setOnGestureListener':
                this.setOnGestureListener(...argv);
                return;
            case 'getRegion':
                return this.getRegion(sessionId, futureId)
            case 'isEnabled':
                return this.isEnabled(sessionId, futureId)
            case 'setEnabled':
                return this.setEnabled(...argv)
        }
        super.onReceive(name, sessionId, futureId, ...argv);
    }

    private getRegion(sessionId: number, futureId: number) {
        const {width, height, x, y} = this.widget.getBoundingClientRect()
        this.onGetRegion(sessionId, futureId, width, height, x, y)
    }

    private onGetRegion(sid, fid, w, h, x, y) {
        [w, h, x, y] = [w, h, x, y].map(n => toAny(n, Int32Value))
        this.sendAction("onGetRegion", sid, fid, w, h, x, y)
    }

    private isEnabled(sessionId: number, futureId: number) {
        // @ts-ignore
        this.sendAction('onIsEnabled', sessionId, futureId, toAny(!this.widget.disabled, BoolValue))
    }

    private setEnabled(...argv) {
        // @ts-ignore
        this.widget.disabled = !fromAny(argv[0], BoolValue)
    }

    private click() {
        this.widget?.click()
    }

    private focus() {
        this.widget?.focus()
    }

    private getAccessibilityLabel(sessionId: number, futureId: number) {
        const text = this.widget.ariaLabel
        this.sendAction('onGetAccessibilityLabel', sessionId, futureId, toAny(text, StringValue))
    }

    private setAccessibilityLabel(...argv) {
        this.widget.ariaLabel = fromAny(argv[0], StringValue)
    }

    private setLocation(...argv: any[]) {
        const [left, top] = argv.slice(0, 2).map(n => fromAny(n, Int32Value) + "px")
        Object.assign(this.widget.style, {left, top})
    }

    private setBackgroundColor(...argv: any[]) {
        this.widget.style["backgroundColor"] = fromAny(argv[0], StringValue);
    }

    private setOnGestureListener(...argv: any[]) {
        const interval = argv.length > 1 ? fromAny(argv[1], Int32Value) : 20;
        let [oldX, oldY] = [0, 0];

        const handle = async (x: number, y: number, pressed: boolean, count: number) => {
            [oldX, oldY] = [x, y];
            await this.sendCallback(argv[0],
                toAny(x, Int32Value), toAny(y, Int32Value), toAny(pressed, BoolValue), toAny(count, Int32Value));
        }

        this.widget.addEventListener("touchstart", async evt => {
            const touch = evt.touches[0] as Touch;
            await handle(touch.pageX, touch.pageY, true, evt.touches.length);
        })
        this.widget.addEventListener("touchmove", async evt => {
            const touch = evt.touches[0] as Touch;
            const [x, y] = [touch.pageX, touch.pageY]
            if (Math.hypot(x - oldX, y - oldY) > interval) {
                await handle(x, y, true, evt.touches.length);
            }
        })
        this.widget.addEventListener("touchend", async () => {
            // 在touchend事件当中没有touches对象,传递最后的坐标以及手指数量1
            await handle(oldX, oldY, false, 1)
        })
    }

    private setOnClickListener(...argv: any[]) {
        this.clickEvent = argv[0];
        this.widget.addEventListener("click", async ()=>{
            if(this.longEvent==null) {
                await this.sendCallback(this.clickEvent);
            }
        })
    }

    private setOnLongClickListener(...argv: any[]) {
        this.longEvent = argv[0];
        this.handleClick();
    }

    private handleClick() {
        const self = this
        const start = () => {
            self.longClick = 0;
            self.clickTimeOutEvent = setTimeout(
                () => self.longClick = 1, MAX_LONG_CLICK_TIMEOUT);
        }
        const move = e => {
            clearTimeout(self.clickTimeOutEvent);
            self.clickTimeOutEvent = 0;
            e.preventDefault();
        }
        const end = async e => {
            clearTimeout(self.clickTimeOutEvent)
            if (self.clickTimeOutEvent != 0) {
                if (self.longClick == 1 && self.longEvent != null) {
                    await self.sendCallback(self.longEvent);
                    e.preventDefault();
                    return;
                }
                if (self.clickEvent != null) {
                    await self.sendCallback(self.clickEvent);
                    // e.preventDefault();
                }
            }
        }
        if ('ontouchstart' in window) {
            this.widget.addEventListener("touchstart", _e => start());
            this.widget.addEventListener("touchmove", e => move(e));
            this.widget.addEventListener("touchend", async e => await end(e))
        } else {
            this.widget.addEventListener("mousedown", _e => start());
            this.widget.addEventListener("mousemove", e => move(e));
            this.widget.addEventListener("mouseup", async e => await end(e))
        }
    }
}

export abstract class WidgetBaseTextSession extends WidgetBaseSession {
    protected widget: HTMLElement

    abstract createWidget(): HTMLElement;

    onCreate(sessionId: number, futureId: number, ...argv) {
        super.onCreate(sessionId, futureId, ...argv);
        this.setText(argv[4])
    }

    onReceive(name: String, sessionId: number, futureId: number, ...argv): void {
        switch (name) {
            case 'getText':
                return this.getText(sessionId, futureId, ...argv)
            case 'setText':
                return this.setText(...argv)
        }
        super.onReceive(name, sessionId, futureId, ...argv);
    }

    private getText(sessionId: number, futureId: number, ..._argv) {
        const text = (this.widget.tagName == 'INPUT')
            ? (this.widget as HTMLInputElement).value
            : this.widget.textContent
        this.sendAction('onGetText', sessionId, futureId, toAny(text, StringValue))
    }

    private setText(...argv) {
        const text = fromAny(argv[0], StringValue)
        if (this.widget.tagName == 'INPUT') {
            const input = this.widget as HTMLInputElement
            if (input.type != 'file')
                input.value = text
        } else {
            this.widget.textContent = text
        }
        this.widget.title = text
    }
}